for
loop is very sophisticated
and includesfor
loops:for sources [ conditional ] do expressionIn each form of the
for sources [ conditional ] collect [ into collection ]
[ by function ] [ as collectionClass ] expression
for sources [ conditional ] select expression [ into collection ]
[ byfunction
]
[
as
collectionClass
]
if
conditional
for
expression,
sources is the source of iteration and
conditional is an additional, optional test that can
be used to control the loop. for
loop is to
execute an expression a specified number of times. This
iteration can take three forms: for numericExpr do expressionIn the first form, numericExpr is the number of times to iterate, or an expression that results in a number; and expression is the expression, often a compound expression, to evaluate at every iteration. The expression is evaluated the number of times specified.
for item := rangeOrCollection do expression
for item in rangeOrCollectiondo
expression
for 9 do print "hello"
allSpaces := #()
for 52 do append allSpaces " "
The last two forms are for iterating over ranges or
collections. The item part of the for
loop names the local iteration variable, which holds
successive values from the range or collection as the loop
iterates. This local iteration variable is available within
the body of the loop. The rangeOrCollection part is
either a range, as described below, or a collection such as an
array. Finally, the expression part of the loop, as
in the previous form, is an expression, often a compound
expression, that is evaluated at each iteration. Note that
item :=
and item in
are equivalent
forms, and both ranges and collections can be used with either
form.
In all of these forms, the for
loop returns the
last value of the loop body expression when the loop ends.
Here are two examples of using this form of for
loop. The sections that follow supply more examples.
global i, myArray := #(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
for i := 1 to 10 do print myArray[i]
global theMin := 25
for i in #(1, 435, 234, 23, 8) do
if i < theMin do theMin := i
undefined
The for
loop evaluates to undefined
because in the last iteration, the if
expression
evaluates to undefined
; theMin, however,
has a value of 1, as you would expect.
A ScriptX for
expression actually works by
creating an object called an iterator. Within a for loop, a script can perform many
operations that would be impossible without access to this iterator. Iterators, an
advanced topic, are discussed in the "Collections" chapter of the ScriptX
Components Guide. Also note that when you want to minimize memory usage and garbage
collection, you can avoid creating an iterator by using the
forEach
method on a collection instead of using a for
expression.
Range
object.startValueThe startValue side of the range is the number from which to start iterating, the endValue side is the number with which to end, and the optional increment is the amount by which to increment at each iteration. Ifto
endValue
[ by
increment ]
by
increment is omitted, the increment
is assumed to be 1. for i := 1 to 23 do print i
for i in 0 to 100 by 10 do print (i * i)
To decrease the index at each iteration, simply reverse the order of the startvalue and endvalue of the range. For decreasing ranges, you must always state the amount by which to decrement:
for i := 10 to 1 by -1 do print (i + i)
The range syntax described here is actually a range literal
that creates an instance of one of the subclasses of
Range
. That literal can be used anywhere in a
script. Ranges are described in greater detail in the section
on "Ranges" beginning on page 153, and in the "Collections"
chapter of the ScriptX Components Guide.
for
loops to iterate over collections
such as arrays and linked lists. At each iteration, each item
in the collection in turn is assigned to the iteration
variable (i
in these examples). The loop body is
then evaluated with the local iteration variable set to that
value.for i in #(1,2,3,4) do print i
for i := #("Francois","Inez","Louis","Margot") do
format debug "%* did it!\n" i @unadorned
Note that ranges, described in the previous section, are also
collections, which allows you to form for
expressions like this:
global countdown := 10 to 1 by -1
for i in countdown do
(format debug "%* seconds to blast off!\n" i @normal)
for
loops can have multiple sources of
iteration, separated by commas. Each source is iterated at
each loop. The loop terminates when any one of the sources of
iteration ends.-- in this example, the first iterator terminates first
for 14, i := 1 to 1000 by 23 do
(prin i @normal debug; prin " " @unadorned debug)
1 24 47 70 93 116 139 162 185 208 231 254 277 300 OK
-- in the following example, the second iterator terminates first
for 14, i := 1 to 1000 by 230 do
(prin i @normal debug; prin " " @unadorned debug)
1 231 461 691 921 OK
for i := 1 to 10, j := 1 to 10 by 2 do (
format debug "I is %1. J is %2\n" #(i, j) #(@normal,@normal)
print (i * j)
)
-- print only the first 8 elements in the array listOfPeople
for 8, i in listofPeople do print i
for
expressions you can also specify an
additional, optional test to stop each form of the loop from
further iterations. Using tests in addition to standard
iterations allows both the simplicity of for
loops and the flexibility of a repeat
loop.for source while conditionalIn this form of thedo
expressionfor source until conditional
do
expression
for
expression, the
source part of the loop is any of the forms
described in the previous sections, and conditional
is an expression that returns false
or an object
whose value is not false
.x := 1
for 23 while x <= 5 do (
print x
x := x + 1
)
1
2
3
4
5
6
You see 1
through 6
in the Listener
window because the for
expression above prints
1
through 5
and then returns
6
, which is the value of the for
expression (that is, the value of x
, the last
expression inside the for
expression) at the end
of the loop.
Here's the same loop performed using until
rather
than while
:
x := 1
for 23 until x > 5 do (
print x
x := x + 1
)
1
2
3
4
5
6
Here's another example using while
:
for m := 1 to 100, n := 1 to 100 by 5 while m * n * n < 10000 do (
prin m @normal debug; prin ", " @unadorned debug
prinLn n @normal debug
)
for
loops in the previous sections all used the do
reserved word to begin an expression that is evaluated each time the loop iterates. The for
loop also has forms that allow you to collect the results of an iterated expression into a collection such as an array, or to collect a particular value of an iterated expression based on a given test.forIn both forms, source is the source (or sources) of iteration, as described in "Simple Iteration" on page 84 and conditional is the optionalsources [ (while|until) conditional ] collect
[ into collection ] [ by function]
[ as collectionClass ] expression
for sources [ (while|until)conditional ] select expression
[ into collection ] [ by function ] [ as collectionClass ] if conditional
while
or until
test described in "Additional Tests."for i := 1 to 10 collect i
#(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
sinvals := for i := 1 to 3 collect sin i
#(0.841470984807897, 0.909297426825682, 0.141120008059867)
The select
form allows you to select and assemble a collection of the values of the loop at each iteration based on a given test. If the test returns true
, the value of expression at each iteration is put into a collection (an array by default).
Note that to use a complex expression in the expression part of the select
form, you must specify that expression inside parentheses. The only expressions that can be used without parentheses in the select
form of the for
loop are:
myArray[i]
)
myRect.x1
)
nextMethod
(see "Defining Class and Instance Variables" on page 140 of Chapter 6, "Defining Classes and Objects")
negativesins := for i := 1 to 10 select (sin i) if (sin i) < 0
#(-0.756802, -0.958924, -0.279415, -0.544021)
global status := #(@doorClosed:false, @keyInIgnition:true,
@propellerMoving:true)
print status
#(@doorClosed:false, @keyInIgnition:true, @propellerMoving:true)
for i in #(@doorClosed, @keyInIgnition, @propellerMoving)
select i if status[i] = true
#(@keyInIgnition, @propellerMoving)
into
clause allows you to append collected or selected items to an existing collection such as an array:squares := #()
for s in 1 to 10 collect into squares (s * s)
The into
clause is most useful for several disconnected (either sequential or nested) for
loops that modify the same collection:
nums := for i in 1 to 5 collect i
for i in 20 to 23 collect into nums i
for i in 40 to 45 collect into nums i
prin nums @complete debug
#(1, 2, 3, 4, 5, 20, 21, 22, 23, 40, 41, 42, 43, 44, 45)
global doors, openDoors := #()
doors := #(@front:false,@back:true,@side:true,@garage:false)
for i in #(@front,@back,@side,@garage)
select i into openDoors if doors[i] = true
#(@back, @side)
Note that the expression following the reserved word into
must evaluate to an instance of a collection. That collection expression can be one of the following expressions:
#(1, 2, 3)
)
myArray[i]
)
myrect.x1
)
nextMethod
(see the discussion of nextMethod
on page 130 of Chapter 6, "Defining Classes and Objects")
The optional by
clause in either the collect
or select
form of the for
expression allows you to specify a function that is used to add each new item to the final collection. This function, which is called a collector, can be either a regular function or a generic function that has the required form. To be used as a collector, a function must return the collection into which the items are being collected.
The collector function is called with two arguments on each pass through the loop. The first argument is a collection (specified by the into
clause, or created based on the as
clause). The second argument is the current value of the loop body. Several generic functions in the Collection protocol have the required form, and others can easily be embodied in a scripted function. The following script joins a sequence of collections into one using merge
, a generic function that every Collection
object provides a method for.
global firstArray := #(1,2,3,4,5)
global secondArray := #(@yes,@no,@maybe)
global thirdArray := #("sow","ewe","cow","hen")
bigJumbledArray := #(firstArray,secondArray,thirdArray)
niceNeatArray := #()
for i in bigJumbledArray collect into niceNeatArray by merge i
#(1, 2, 3, 4, 5, @yes, @no, @maybe, "sow", "ewe", ...)
By default, a for
expression calls the appendReturningSelf
global function, which is defined for sequences. This global function is similar to append
, except that it returns the collection itself. (By contrast, the generic function append
returns the key of the item that was appended.) Another useful function for collecting items is the global function addManyValues
.
In the following example, items are collected into a string using a function that is built around prepend
, a generic function for which all sequences define a method. This has the effect of reversing the order of a string. (Keep in mind that prepend
is an inefficient way to add items to some sequences, including arrays and strings. It is used here only for illustration purposes.) For more information on functions, see Chapter 5, "Functions, Threads and Pipes."
global reagan := new String string:""
speech := "Facts are stupid things"
-- define a function that can act as a collector or selector
function prependReturningSelf sequence value -> (
prepend sequence value
return sequence
)
-- use the collector function to reverse the string
for i in speech collect into reagan by prependReturningSelf i
"sgniht diputs era stcaF"
For a variation on this script which uses an anonymous function, see page 101 of Chapter 5, "Functions, Threads and Pipes."
myArray[i]
)
myRect.x1
)
nextMethod
(see "Class and Instance Methods" on page 116 of Chapter 6, "Defining Classes and Objects"
by
clause, you must specify them within parentheses.as
clause specifies a collection class into which elements are collected or selected. Collections are described in Chapter 7, "Collections." Note especially the section on strings as collections.for i in "jabberwocky" collect as Array i
#(106, 97, 98, 98, 101, 114, 119, 111, 99, 107, ...)
for i in "jabberwocky" select i as String if i > 105
"jrwoky"
The first example collects characters in a string literal (a StringConstant
object) into an array. They are coerced to Unicode values, which are ImmediateInteger
objects, before being added to the array. The second example selects only those elements of a string for which the Unicode value is greater than 105
.
The expression following the as
reserved word must evaluate to a class. The types of expressions that can be specified after as
without parentheses are the same as for the by
clause, listed above. To specify a complex expression in an as
clause, you must specify it within parentheses.
This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.